package _thread_lock._concurrency.leakybucket;

import java.util.concurrent.TimeUnit;

/**
 * @Author: huangya
 * @Date: 2020/11/18
 * @Description: 漏桶算法
 */
public class LeakyBucket {

    /** 桶的大小 */
    private double burst;

    /** 水流出速率：单位数量 */
    private double rate;

    /** 单位 */
    private TimeUnit timeUnit;

    /** 最后更新时间 */
    private volatile long refreshTime;

    /** 桶里面的水量 */
    private int water;

    private Circulation circulation;

    private DenyPolicy denyPolicy;

    public LeakyBucket(double burst, double rate) {
        this(burst, rate, TimeUnit.MILLISECONDS);
    }

    public LeakyBucket(double burst, double rate, TimeUnit timeUnit) {
        this(burst, rate, timeUnit, new AbortDenyPolicy());
    }

    public LeakyBucket(double burst, double rate, TimeUnit timeUnit, DenyPolicy denyPolicy) {
        if(timeUnit==null){
            throw new NullPointerException();
        }
        this.burst = burst;
        this.rate = rate;
        this.timeUnit = timeUnit;
        if(timeUnit.ordinal()<2){
            //纳秒
            this.circulation = new NanosecondsCirculation();
        }else{
            //毫秒
            this.circulation = new MillisecondsCirculation();
        }
        this.denyPolicy = denyPolicy;
    }

    /**
     * 刷新桶的水量
     */
    public void refreshWater(){
        long now = circulation.now();
        if(water>0){
            circulation.doRefreshWater(now);
        }
        refreshTime = now;
    }

    public synchronized boolean tryAcquire(){
        refreshWater();
        if(water < burst){
            water++;
            //todo.. do something..
            return true;
        }else{
            //todo.. do something..
            //denyPolicy.reject();
            return false;
        }
    }


    abstract class Circulation {

        protected TimeUnit circulationTimeUnit;

        abstract long now();

        public void doRefreshWater(long now){
            water = (int) Math.max(0, water - (now-refreshTime)/circulationTimeUnit.convert(1L, timeUnit)*rate);
        }
    }

    private class MillisecondsCirculation extends Circulation {

        public MillisecondsCirculation() {
            circulationTimeUnit = TimeUnit.MILLISECONDS;
        }

        @Override
        public long now() {
            return System.currentTimeMillis();
        }

    }

    private class NanosecondsCirculation extends Circulation {

        public NanosecondsCirculation() {
            circulationTimeUnit = TimeUnit.NANOSECONDS;
        }

        @Override
        public long now() {
            return System.nanoTime();
        }
    }


}
